﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Linq.Expressions;

namespace ExpressionEvalTest
{
    [TestClass]
    public class LinqTests
    {

        #region Additional test attributes
        //
        // You can use the following additional attributes as you write your tests:
        //
        // Use ClassInitialize to run code before running the first test in the class
        // [ClassInitialize()]
        // public static void MyClassInitialize(TestContext testContext) { }
        //
        // Use ClassCleanup to run code after all tests in a class have run
        // [ClassCleanup()]
        // public static void MyClassCleanup() { }
        //
        // Use TestInitialize to run code before running each test 
        // [TestInitialize()]
        // public void MyTestInitialize() { }
        //
        // Use TestCleanup to run code after each test has run
        // [TestCleanup()]
        // public void MyTestCleanup() { }
        //
        #endregion

        [TestMethod]
        public void Select()
        {
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, List<LinqTest>>>(
               (linq) => linq.ToList(),
               @"linq.ToList()",
               new object[] { GetLinqData() },
               Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, LinqTest[]>>(
               (linq) => linq.ToArray(),
               @"linq.ToArray()",
               new object[] { GetLinqData() },
               Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, List<LinqTest>>>(
               (linq) => linq.Select(item => item).ToList(),
               @"linq.Select(item => item).ToList()",
               new object[] { GetLinqData() },
               Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
        }

        [TestMethod]
        public void Where()
        {
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, List<LinqTest>>>(
               (linq) => linq.Where(item => item.NullableInt.HasValue && item.NullableInt.Value == 1).ToList(),
               @"linq.Where(item => item.NullableInt.HasValue && item.NullableInt.Value == 1).ToList()",
               new object[] { GetLinqData() },
               Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, List<LinqTest>>>(
               (linq) => linq.Where(item => item.Text == "First").ToList(),
               @"linq.Where(item => item.Text == ""First"").ToList()",
               new object[] { GetLinqData() },
               Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, LinqTest[]>>(
               (linq) => linq.Where(item => item.Integer > 3).ToArray(),
               @"linq.Where(item => item.Integer > 3).ToArray()",
               new object[] { GetLinqData() },
               Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, List<LinqTest>>>(
               (linq) => linq.Where(item => item.Date > new DateTime(2009, 4, 3)).Select(item => item).ToList(),
               @"linq.Where(item => item.Date > new DateTime(2009, 4, 3)).Select(item => item).ToList()",
               new object[] { GetLinqData() },
               Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
        }

        [TestMethod]
        public void Anonymous()
        {
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, List<string>>>(
               (linq) => linq.Where(item => item.Text == "First")
                   .Select(item => new { Text = item.Text })
                   .Select(item => item.Text)
                   .ToList(),
               @"linq.Where(item => item.Text == ""First"")
                    .Select(item => new { Text = item.Text })
                    .Select(item => item.Text)
                    .ToList()",
               new object[] { GetLinqData() },
               Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, int[]>>(
               (linq) => linq.Where(item => item.Integer > 3).Select(item => new { Text = item.Integer }).Select(item => item.Text).ToArray(),
               @"linq.Where(item => item.Integer > 3)
                    .Select(item => new { Text = item.Integer })
                    .Select(item => item.Text)
                    .ToArray()",
               new object[] { GetLinqData() },
               Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, List<DateTime>>>(
               (linq) => linq.Where(item => item.Date > new DateTime(2009, 4, 3)).Select(item => new { date = item.Date }).Select(item => item.date).ToList(),
               @"linq.Where(item => item.Date > new DateTime(2009, 4, 3))
                    .Select(item => new { date = item.Date })
                    .Select(item => item.date)
                    .ToList()",
               new object[] { GetLinqData() },
               Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, List<string>>>(
               (linq) => linq.Where(item => item.Text == "First")
                   .Select(item => new { item.Text })
                   .Select(item => item.Text)
                   .ToList(),
               @"linq.Where(item => item.Text == ""First"")
                    .Select(item => new { item.Text })
                    .Select(item => item.Text)
                    .ToList()",
               new object[] { GetLinqData() },
               Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
        }

        [TestMethod]
        public void Paging()
        {
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, List<string>>>(
               (linq) => linq.Where(item => item.Text == "First").Select(item => new { Text = item.Text }).Select(item => item.Text).Take(2).ToList(),
               @"linq.Where(item => item.Text == ""First"")
                    .Select(item => new { Text = item.Text })
                    .Select(item => item.Text)
                    .Take(2).ToList()",
               new object[] { GetLinqData() },
               Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, int[]>>(
               (linq) => linq.Where(item => item.Integer > 3).Select(item => new { Text = item.Integer }).Select(item => item.Text).Skip(2).ToArray(),
               @"linq.Where(item => item.Integer > 3)
                    .Select(item => new { Text = item.Integer })
                    .Select(item => item.Text)
                    .Skip(2).ToArray()",
               new object[] { GetLinqData() },
               Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, List<DateTime>>>(
               (linq) => linq.Where(item => item.Date > new DateTime(2009, 4, 3)).Select(item => new { date = item.Date }).Select(item => item.date).Take(4).Skip(2).ToList(),
               @"linq.Where(item => item.Date > new DateTime(2009, 4, 3))
                    .Select(item => new { date = item.Date })
                    .Select(item => item.date)
                    .Take(4).Skip(2).ToList()",
               new object[] { GetLinqData() },
               Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
        }

        [TestMethod]
        public void Count()
        {
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, int>>(
               (linq) => linq.Where(item => item.Text == "First").Select(item => new { Text = item.Text }).Select(item => item.Text).Count(),
               @"linq.Where(item => item.Text == ""First"")
                                .Select(item => new { Text = item.Text })
                                .Select(item => item.Text)
                                .Count()",
               new object[] { GetLinqData() },
               Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, int>>(
               (linq) => linq.Count(item => item.Integer > 3),
               @"linq.Count(item => item.Integer > 3)",
               new object[] { GetLinqData() },
               Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
        }

        [TestMethod]
        public void First()
        {
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, LinqTest>>(
               (linq) => linq.Where(item => item.Text == "First").First(),
               @"linq.Where(item => item.Text == ""First"")
                                .First()",
               new object[] { GetLinqData() },
               Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, LinqTest>>(
               (linq) => linq.First(item => item.Integer > 3),
               @"linq.First(item => item.Integer > 3)",
               new object[] { GetLinqData() },
               Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
        }

        [TestMethod]
        [ExpectedException(typeof(System.Reflection.TargetInvocationException))]
        public void FirstFail()
        {
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, LinqTest>>(
               (linq) => linq.Where(item => item.Text == "NotExisting").First(),
               @"linq.Where(item => item.Text == ""NotExisting"")
  
                                            .First()",
               new object[] { GetLinqData() },
               Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
        }

        [TestMethod]
        public void FirstOrDefault()
        {
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, LinqTest>>(
               (linq) => linq.Where(item => item.Text == "First").FirstOrDefault(),
               @"linq.Where(item => item.Text == ""First"")
                                .FirstOrDefault()",
               new object[] { GetLinqData() },
               Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, LinqTest>>(
               (linq) => linq.FirstOrDefault(item => item.Integer > 3),
               @"linq.FirstOrDefault(item => item.Integer > 3)",
               new object[] { GetLinqData() },
               Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, LinqTest>>(
               (linq) => linq.Where(item => item.Text == "NotExisting").FirstOrDefault(),
               @"linq.Where(item => item.Text == ""NotExisting"")
  
                                            .FirstOrDefault()",
               new object[] { GetLinqData() },
               Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
        }

        [TestMethod]
        public void Last()
        {
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, LinqTest>>(
               (linq) => linq.Where(item => item.Text == "First").Last(),
               @"linq.Where(item => item.Text == ""First"")
                                .Last()",
               new object[] { GetLinqData() },
               Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, LinqTest>>(
               (linq) => linq.Last(item => item.Integer > 3),
               @"linq.Last(item => item.Integer > 3)",
               new object[] { GetLinqData() },
               Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
        }

        [TestMethod]
        [ExpectedException(typeof(System.Reflection.TargetInvocationException))]
        public void LastFail()
        {
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, LinqTest>>(
               (linq) => linq.Where(item => item.Text == "NotExisting").Last(),
               @"linq.Where(item => item.Text == ""NotExisting"")
                                            .Last()",
               new object[] { GetLinqData() },
               Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
        }

        [TestMethod]
        public void LastOrDefault()
        {
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, LinqTest>>(
               (linq) => linq.Where(item => item.Text == "First").LastOrDefault(),
               @"linq.Where(item => item.Text == ""First"")
                                .LastOrDefault()",
               new object[] { GetLinqData() },
               Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, LinqTest>>(
               (linq) => linq.LastOrDefault(item => item.Integer > 3),
               @"linq.LastOrDefault(item => item.Integer > 3)",
               new object[] { GetLinqData() },
               Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, LinqTest>>(
               (linq) => linq.Where(item => item.Text == "NotExisting").LastOrDefault(),
               @"linq.Where(item => item.Text == ""NotExisting"")
  
                                            .LastOrDefault()",
               new object[] { GetLinqData() },
               Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
        }

        [TestMethod]
        public void Sum()
        {
            var result = GetLinqData()
                .GroupBy(item => item.Position)
                .Select(group => new { key = group.Key, sum = group.Sum(item => item.Integer)})
                .Select(item => item.sum)
                .ToArray();
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, int>>(
              (linq) => linq.Select(item => item.Integer).Sum(),
              @"linq.Select(item => item.Integer).Sum()",
              new object[] { GetLinqData() },
              Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, int>>(
               (linq) => linq.Sum(item => item.Integer),
               @"linq.Sum(item => item.Integer)",
               new object[] { GetLinqData() },
               Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, int[]>>(
               (linq) => linq.GroupBy(item => item.Position)
                .Select(group => new { key = group.Key, sum = group.Sum(item => item.Integer)})
                .Select(item => item.sum)
                .ToArray(),
               @"linq.GroupBy(item => item.Position)
                .Select(group => new { key = group.Key, sum = group.Sum(item => item.Integer)})
                .Select(item => item.sum)
                .ToArray()",
               new object[] { GetLinqData() },
               Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
        }

        [TestMethod]
        public void Average()
        {
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, double>>(
              (linq) => linq.Select(item => item.Integer).Average(),
              @"linq.Select(item => item.Integer).Average()",
              new object[] { GetLinqData() },
              Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, double>>(
               (linq) => linq.Average(item => item.Integer),
               @"linq.Average(item => item.Integer)",
               new object[] { GetLinqData() },
               Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, double[]>>(
               (linq) => linq.GroupBy(item => item.Position)
                .Select(group => new { key = group.Key, sum = group.Average(item => item.Integer) })
                .Select(item => item.sum)
                .ToArray(),
               @"linq.GroupBy(item => item.Position)
                .Select(group => new { key = group.Key, sum = group.Average(item => item.Integer)})
                .Select(item => item.sum)
                .ToArray()",
               new object[] { GetLinqData() },
               Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
        }

        [TestMethod]
        public void Max()
        {
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, DateTime[]>>(
    (linq) => linq.GroupBy(item => item.Position)
                  .Select(group => new { key = group.Key, sum = group.Max(item => item.Date) })
                  .Select(item => item.sum)
                  .ToArray(),
    @"linq.GroupBy(item => item.Position)
                .Select(group => new { key = group.Key, sum = group.Max(item => item.Date)})
                .Select(item => item.sum)
                .ToArray()",
    new object[] { GetLinqData() },
    Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, int>>(
                (linq) => linq.Select(item => item.Integer).Max(),
                @"linq.Select(item => item.Integer).Max()",
                new object[] {GetLinqData()},
                Expression.Parameter(typeof (IQueryable<LinqTest>), "linq"));
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, int>>(
                (linq) => linq.Max(item => item.Integer),
                @"linq.Max(item => item.Integer)",
                new object[] {GetLinqData()},
                Expression.Parameter(typeof (IQueryable<LinqTest>), "linq"));
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, int[]>>(
                (linq) => linq.GroupBy(item => item.Position)
                              .Select(group => new {key = group.Key, sum = group.Max(item => item.Integer)})
                              .Select(item => item.sum)
                              .ToArray(),
                @"linq.GroupBy(item => item.Position)
                .Select(group => new { key = group.Key, sum = group.Max(item => item.Integer)})
                .Select(item => item.sum)
                .ToArray()",
                new object[] {GetLinqData()},
                Expression.Parameter(typeof (IQueryable<LinqTest>), "linq"));
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, DateTime>>(
                (linq) => linq.Max(item => item.Date),
                @"linq.Max(item => item.Date)",
                new object[] {GetLinqData()},
                Expression.Parameter(typeof (IQueryable<LinqTest>), "linq"));

        }

        [TestMethod]
        public void Min()
        {
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, int>>(
              (linq) => linq.Select(item => item.Integer).Min(),
              @"linq.Select(item => item.Integer).Min()",
              new object[] { GetLinqData() },
              Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, int>>(
               (linq) => linq.Min(item => item.Integer),
               @"linq.Min(item => item.Integer)",
               new object[] { GetLinqData() },
               Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, int[]>>(
               (linq) => linq.GroupBy(item => item.Position)
                .Select(group => new { key = group.Key, sum = group.Min(item => item.Integer) })
                .Select(item => item.sum)
                .ToArray(),
               @"linq.GroupBy(item => item.Position)
                .Select(group => new { key = group.Key, sum = group.Min(item => item.Integer)})
                .Select(item => item.sum)
                .ToArray()",
               new object[] { GetLinqData() },
               Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
        }

        [TestMethod]
        public void Join()
        {
            var result = GetLinqData().SelectMany(item => item.Relations, (test, test2) => test2).ToArray();
            ExpressionTreeBuilderTest.CompareLambdaWithExpressionEval<Func<IQueryable<LinqTest>, LinqTest2[]>>(
              (linq) => linq.SelectMany(item => item.Relations, (test, test2) => test2).ToArray(),
              @"linq.SelectMany(item => item.Relations, (test, test2) => test2).ToArray()",
              new object[] { GetLinqData() },
              Expression.Parameter(typeof(IQueryable<LinqTest>), "linq"));
        }

        public static IQueryable<LinqTest> GetLinqData()
        {
            List<LinqTest> data = new List<LinqTest>() {
                new LinqTest {
                    Integer = 3,
                    Text = "First",
                    Date = new DateTime(2009, 4, 1),
                    Float = 3.5,
                    Position = TestEnum.FirstItem,
                    NullableInt = 1, 
                    Relations = new List<LinqTest2> { new LinqTest2 { Value = "1" },  new LinqTest2 { Value = "2" }}
                },
                new LinqTest {
                    Integer = 1,
                    Text = "Next1",
                    Date = new DateTime(2009, 4, 1),
                    Float = 1.2,
                    Position = TestEnum.CommonItem,
                    NullableInt = 2, 
                    Relations = new List<LinqTest2> { new LinqTest2 { Value = "1" },  new LinqTest2 { Value = "3" }}
                },
                new LinqTest {
                    Integer = 2,
                    Text = "Next2",
                    Date = new DateTime(2009, 4, 2),
                    Float = 2.2,
                    Position = TestEnum.CommonItem,
                    Relations = new List<LinqTest2> { new LinqTest2 { Value = "4" },  new LinqTest2 { Value = "5" }}
                },
                new LinqTest {
                    Integer = 3,
                    Text = "Next3",
                    Date = new DateTime(2009, 4, 3),
                    Float = 3.2,
                    Position = TestEnum.CommonItem,
                    Relations = new List<LinqTest2> { new LinqTest2 { Value = "6" },  new LinqTest2 { Value = "7" }}
                },
                new LinqTest {
                    Integer = 4,
                    Text = "Next4",
                    Date = new DateTime(2009, 4, 4),
                    Float = 4.2,
                    Position = TestEnum.CommonItem,
                    Relations = new List<LinqTest2> {}
                },
                new LinqTest {
                    Integer = 4,
                    Text = "Next4",
                    Date = new DateTime(2009, 4, 4),
                    Float = 4.2,
                    Position = TestEnum.CommonItem,
                    Relations = new List<LinqTest2> {}
                },
                new LinqTest {
                    Integer = 3,
                    Text = "Last",
                    Date = new DateTime(2009, 4, 20),
                    Float = 3.5,
                    Position = TestEnum.LastItem,
                    Relations = new List<LinqTest2> {}
                },
            };

            return data.AsQueryable();
        }
        public class LinqTest
        {
            public int Integer { get; set; }
            public string Text { get; set; }
            public DateTime Date { get; set; }
            public double Float { get; set; }
            public TestEnum Position { get; set; }
            public int? NullableInt { get; set; }
            public IEnumerable<LinqTest2> Relations { get; set; }
        }

        public class LinqTest2
        {
            public string Value { get; set; }
        }
    }
}
